﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Dynamic;

namespace Sims3Package.Wrapper
{

    public class OBJKResourceWrapper :  Wrapper
    {
        public new static List<UInt32> ResourceTypes { get { return new List<uint>(mResourceTypes); } }
        private new static UInt32[] mResourceTypes = { 0x02DC343FU };

        public Int32 Version { get; private set; }
        private List<Component> ComponentList { get; set; }
        public List<ComponentData> ComponentDataList { get; set; }
        public List<TGIBlock> TGIBlockList { get; set; }
        public OBJKResourceWrapper(Stream s) : base(s) { stream = s; if (stream != null) { stream.Position = 0; Parse(); } else { ParseNull(); } }
        public VisibilityFlag Visibility { get; set; }

        private void ParseNull()
        {
            Version = 7;
            ComponentList = new List<Component>();
            ComponentDataList = new List<ComponentData>();
            this.TGIBlockList = new List<TGIBlock>();
        }

        private void Parse()
        {
            BinaryReader r = new BinaryReader(stream);
            Version = r.ReadInt32();
            int tgiOffest = r.ReadInt32() + (int)stream.Position;   // Absolute position
            int tgiCount = (r.ReadInt32() - 4) / 16;    // from TGI size to count
            byte count1 = r.ReadByte();

            ComponentList = new List<Component>(count1);
            for (byte i = 0; i < count1; i++)
                this.ComponentList.Add((Component)r.ReadUInt32());
            
            byte count2 = r.ReadByte();
            ComponentDataList = new List<ComponentData>(count2);
            for (byte i = 0; i < count2; i++)
            {
                ComponentDataList.Add(new ComponentData(stream));
            }

            this.Visibility = (VisibilityFlag)r.ReadByte();
            if (stream.Position != tgiOffest) throw new InvalidDataException("Position is not correct");
            this.TGIBlockList = new List<TGIBlock>(tgiCount);
            int tgiCountCheck = r.ReadInt32(); if (tgiCountCheck != tgiCount) throw new InvalidDataException("TGI Count is not correct");
            for (int i = 0; i < tgiCount; i++)
            {
                this.TGIBlockList.Add(new TGIBlock(stream, TGIOrder.TGI));
            }
            if (stream.Position != stream.Length) throw new Exception("position is not correct");

        }
        public override Stream UnParse()
        {
            MemoryStream s = new MemoryStream();
            BinaryWriter w = new BinaryWriter(s);
            w.Write(this.Version);

            // Calculate the size
            int size = 4 + 1 + this.ComponentList.Count * 4 + 1;
            foreach (var c in this.ComponentDataList) size += c.GetSize();
            size += 1;
            w.Write(size); // TGI Offset
            w.Write(this.TGIBlockList.Count);   // TGI Count

            w.Write((byte)this.ComponentList.Count);
            foreach (var c in this.ComponentList) w.Write((uint)c);

            w.Write((byte)this.ComponentDataList.Count);
            foreach (var c in this.ComponentDataList) c.UnParse(s);

            w.Write((byte)this.Visibility);
            foreach (TGIBlock tgi in this.TGIBlockList) tgi.UnParse(s);

            return s;
        }
        public override string Value
        {
            get
            {
                StringBuilder b = new StringBuilder();
                b.AppendFormat("Version: 0x{0:X8}", this.Version).AppendLine();
                b.AppendFormat("ComponentList: Count 0x{0:X2}", this.ComponentList.Count).AppendLine();
                for (int i = 0; i < this.ComponentList.Count; i++) b.AppendFormat("--[{0:X2}]: {1}--0x{2:X8}", i, this.ComponentList[i].ToString(), (uint)this.ComponentList[i]).AppendLine();
                b.AppendLine().AppendFormat("ComponentDataList: Count 0x{0:X2}", this.ComponentDataList.Count).AppendLine();
                foreach (ComponentData c in this.ComponentDataList) b.Append(c.Value).AppendLine();
                b.Append("Visibility: ").Append(this.Visibility.ToString()).AppendLine();
                b.AppendFormat("TGI Block List: Count 0x{0:X2}", this.TGIBlockList.Count).AppendLine();
                foreach (TGIBlock tgi in this.TGIBlockList) b.Append(tgi.ToString()).AppendLine();
                return b.ToString();
            }
        }

        #region Sub-Types
        public enum Component : uint
        {
            Animation = 0xee17c6ad,
            Effect = 0x80d91e9e,
            Footprint = 0xc807312a,
            Lighting = 0xda6c50fd,
            Location = 0x461922c8,
            LotObject = 0x6693c8b3,
            Model = 0x2954e734,
            Physics = 0x1a8feb14,
            Sacs = 0x3ae9a8e7,
            Script = 0x23177498,
            Sim = 0x22706efa,
            Slot = 0x2ef1e401,
            Steering = 0x61bd317c,
            Transform = 0x54cb7ebb,
            Tree = 0xc602cd31,
            VisualState = 0x50b3d17c,
        }

        public enum VisibilityFlag
        {
            VisibleAlways = 0x00,
            VisibleInToolsOnly = 0x01,
            VisibleAsDistantTerrain = 0x02
        }

        public class ComponentData : Resource
        {
            public ComponentData(Stream s) : base(s) { stream = s; stream.Position = s.Position; Parse(); }
            public string Key { get; set; }
            public ComponentDataType DataType { get; set; }
            private void Parse()
            {
                BinaryReader r = new BinaryReader(stream);
                Key = new string(r.ReadChars(r.ReadInt32()));
                this.DataType = new ComponentDataType(stream);

            }

            public override Stream UnParse()
            {
                throw new NotImplementedException();
            }

            public void UnParse(Stream s)
            {
                BinaryWriter w = new BinaryWriter(s);
                w.Write(this.Key.Length);
                w.Write(this.Key.ToCharArray());
                this.DataType.UnParse(s);
            }
            public string Value
            {
                get
                {
                    StringBuilder b = new StringBuilder();
                    b.AppendFormat("ComponentData Key: {0}", this.Key).AppendLine();
                    b.Append(this.DataType.stringValue);
                    return b.ToString();
                }
            }

            public int GetSize()
            {
                return 4 + this.Key.Length + 1 + this.DataType.GetSize();
            }
        }

        public class ComponentDataType :Resource
        {
            public dynamic Value { get { if (this.ControlCode != 0x00 && this.ControlCode != 0x03) { return this.intValue; } else { return this.strValue; } } }
            private string strValue;
            private int intValue;

            public byte ControlCode { get; set; }
            public ComponentDataType(Stream s) : base(s)
            {
                BinaryReader r = new BinaryReader(s);
                ControlCode = r.ReadByte();
                switch (ControlCode)
                {
                    case 0x00: this.strValue = new string(r.ReadChars(r.ReadInt32())); break;
                    case 0x01: this.intValue = r.ReadInt32(); break;
                    case 0x02: this.intValue = r.ReadInt32(); break;
                    case 0x03: this.strValue = new string(r.ReadChars(r.ReadInt32())); break;
                    case 0x04: this.intValue = r.ReadInt32(); break;
                    default:
                        break;
                }
            }

            public int GetSize()
            {
                if (Value is string)
                {
                    return 4 + strValue.Length;
                }
                else
                {
                    return 4;
                }
            }

            public override Stream UnParse()
            {
                throw new NotImplementedException();
            }
            public void UnParse(Stream s)
            {
                BinaryWriter w = new BinaryWriter(s);
                w.Write(ControlCode);
                switch (ControlCode)
                {
                    case 0x00: w.Write(strValue.Length); w.Write(strValue.ToCharArray()); break;
                    case 0x01: w.Write(intValue); break;
                    case 0x02: w.Write(intValue); break;
                    case 0x03: w.Write(strValue.Length); w.Write(strValue.ToCharArray()); break;
                    case 0x04: w.Write(intValue); break;
                    default:
                        break;
                }
            }
            public string stringValue
            {
                get
                {
                    StringBuilder b = new StringBuilder();
                    b.Append("------ValueName:").Append((Value is string)?strValue :intValue.ToString("X2"));
                    return b.ToString();

                }
            }
            
        }
        #endregion
    }

}
